<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    
<meta charset="UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"/>


<meta http-equiv="Cache-Control" content="no-transform" />
<meta http-equiv="Cache-Control" content="no-siteapp" />

<meta name="theme-color" content="#f8f5ec" />
<meta name="msapplication-navbutton-color" content="#f8f5ec">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="#f8f5ec">



  <meta name="description" content="如何实现图片上传API"/>




  <meta name="keywords" content="开发, flask, python, 八一" />



  <meta name="baidu-site-verification" content="HhUstaSjr0" />



  <meta name="google-site-verification" content="UA-102975942-1" />






  <link rel="alternate" href="/atom.xml" title="八一">




  <link rel="shortcut icon" type="image/x-icon" href="/favicon.ico?v=2.6.0" />



<link rel="canonical" href="https://bay1.top/2017/08/19/如何实现图片上传API/"/>


<link rel="stylesheet" type="text/css" href="/css/style.css?v=2.6.0" />
<link rel="stylesheet" type="text/css" href="/css/prettify.css" media="screen" />
<link rel="stylesheet" type="text/css" href="/css/sons-of-obsidian.css" media="screen" />



  <link rel="stylesheet" type="text/css" href="/lib/fancybox/jquery.fancybox.css" />




  
  <script id="baidu_analytics">
    var _hmt = _hmt || [];
    (function() {
      var hm = document.createElement("script");
      hm.src = "https://hm.baidu.com/hm.js?9a885cc9fb6cd7bcef579deb8efe8a70";
      var s = document.getElementsByTagName("script")[0];
      s.parentNode.insertBefore(hm, s);
    })();
  </script>



  <script id="google_analytics">
    (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
        (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
        m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
        })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

        ga('create', 'UA-102975942-1', 'auto');
        ga('send', 'pageview');
  </script>










    <title> 如何实现图片上传API - 八一 </title>
  </head>

  <body><div id="mobile-navbar" class="mobile-navbar">
  <div class="mobile-header-logo">
    <a href="/." class="logo">八一</a>
  </div>
  <div class="mobile-navbar-icon">
    <span></span>
    <span></span>
    <span></span>
  </div>
</div>

<nav id="mobile-menu" class="mobile-menu slideout-menu">
  <ul class="mobile-menu-list">
    
      <a href="/archives">
        <li class="mobile-menu-item">
          
          
            文章
          
        </li>
      </a>
    
      <a href="/tags">
        <li class="mobile-menu-item">
          
          
            标签
          
        </li>
      </a>
    
      <a href="/about">
        <li class="mobile-menu-item">
          
          
            关于/友链
          
        </li>
      </a>
    
      <a href="/search">
        <li class="mobile-menu-item">
          
          
            站内搜索
          
        </li>
      </a>
    
  </ul>
</nav>

    <div class="container" id="mobile-panel">
      <header id="header" class="header"><div class="logo-wrapper">
  <a href="/." class="logo">八一</a>
</div>

<nav class="site-navbar">
  
    <ul id="menu" class="menu">
      
        <li class="menu-item">
          <a class="menu-item-link" href="/archives">
            
            
              文章
            
          </a>
        </li>
      
        <li class="menu-item">
          <a class="menu-item-link" href="/tags">
            
            
              标签
            
          </a>
        </li>
      
        <li class="menu-item">
          <a class="menu-item-link" href="/about">
            
            
              关于/友链
            
          </a>
        </li>
      
        <li class="menu-item">
          <a class="menu-item-link" href="/search">
            
            
              站内搜索
            
          </a>
        </li>
      
    </ul>
  
</nav>

      </header>

      <main id="main" class="main">
        <div class="content-wrapper">
          <div id="content" class="content">
            
  
  <article class="post">
    <header class="post-header">
      <h1 class="post-title">
        
          如何实现图片上传API
        
      </h1>

      <div class="post-meta">
        <span class="post-time">
          2017-08-19
        </span>
        
        
        
      </div>
    </header>

    
    
  <div class="post-toc" id="post-toc">
    <h2 class="post-toc-title">文章目录</h2>
    <div class="post-toc-content">
      <ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#获取图片信息"><span class="toc-text">获取图片信息</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#检查图片文件"><span class="toc-text">检查图片文件</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#图片信息筛选"><span class="toc-text">图片信息筛选</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#多对多关系的储存"><span class="toc-text">多对多关系的储存</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#对外API"><span class="toc-text">对外API</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#删除图片"><span class="toc-text">删除图片</span></a></li></ol>
    </div>
  </div>


    <div class="post-content">
      
        <p>这个暑假主要在搞Flask-RESTful实现的一个图片网站,就肯定少不了上传图片的部分<br>第一次干这样的事，的确很棘手 <a id="more"></a><br>主要是pictures业务逻辑模块的编写</p>
<blockquote>
<p>主要的实现就是<br>获取图片—检查图片文件—图片信息筛选—多对多关系的储存—对外API</p>
</blockquote>
<p>思路似乎看着很简单，，，，，，但是,真的要看很多东西，很多细节</p>
<h2 id="获取图片信息"><a href="#获取图片信息" class="headerlink" title="获取图片信息"></a>获取图片信息</h2><blockquote>
<p>可能是上传图片，也可能是更新图片<br>接收参数包括文件路径，图片描述，图片标签，图片ID(更新时用)</p>
</blockquote>
<figure class="highlight py"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">put_or_upload_picture</span><span class="params">(files,despriction,tags,picture_id=None)</span></span></span><br></pre></td></tr></table></figure>
<h2 id="检查图片文件"><a href="#检查图片文件" class="headerlink" title="检查图片文件"></a>检查图片文件</h2><p>检查文件类型并重命名</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">verify_and_rename_pictures</span><span class="params">(files)</span>:</span></span><br><span class="line">    files=request.files[<span class="string">'files'</span>]</span><br><span class="line">    filename = secure_filename(files.filename)</span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> filename.rsplit(<span class="string">'.'</span>,<span class="number">1</span>)[<span class="number">1</span>] <span class="keyword">in</span> flask.current_app.config[<span class="string">'ALLOWED_EXTENSIONS'</span>]:</span><br><span class="line">        <span class="keyword">return</span> &#123;<span class="string">'error'</span>:<span class="string">'只能上传图片'</span>&#125;</span><br><span class="line">    nowtime = datetime.now()</span><br><span class="line">    <span class="comment">#重命名,format字符串格式化</span></span><br><span class="line">    filename = hashlib.md5(<span class="string">'&#123;0&#125;_&#123;1&#125;'</span>.format(filename,nowtime).encode(<span class="string">"gb2312"</span>)).hexdigest()+<span class="string">"."</span>+filename.rsplit(<span class="string">'.'</span>,<span class="number">1</span>)[<span class="number">1</span>]</span><br><span class="line">    files.save(os.path.join(flask.current_app.config[<span class="string">'UPLOAD_FOLDER'</span>], filename))</span><br><span class="line">    <span class="keyword">return</span> filename</span><br></pre></td></tr></table></figure>
<blockquote>
<p>from werkzeug.utils import secure_filename<br>这是检查文件的一个安全函数</p>
</blockquote>
<p>来自官网的解释：<strong style="color:red;">假定 ../ 的数量是正确的，而您会将这串字符与 UPLOAD_FOLDER 所指定的路径相连接,那么这个用户就可能有能力修改服务器文件系统上的一个文件，而他不应该拥有这种权限</strong></p>
<p>这个函数所做的事情就是将../注释掉<br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">&gt;&gt;&gt; secure_filename(<span class="string">'../../../../home/username/.bashrc'</span>)</span><br><span class="line"><span class="string">'home_username_.bashrc'</span></span><br></pre></td></tr></table></figure></p>
<blockquote>
<p>ALLOWED_EXTENSIONS,UPLOAD_FOLDER已经在config中配置,就是文字表面意思</p>
</blockquote>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">#上传图片配置</span></span><br><span class="line">UPLOAD_FOLDER = os.getcwd()+<span class="string">'/app/uploadpic/'</span></span><br><span class="line">MAX_CONTENT_LENGTH = <span class="number">16</span> * <span class="number">1024</span> * <span class="number">1024</span> <span class="comment">#文件最大限制16M,仅需配置</span></span><br><span class="line">ALLOWED_EXTENSIONS = set([<span class="string">'bmp'</span>,<span class="string">'svg'</span>,<span class="string">'png'</span>,<span class="string">'jpg'</span>,<span class="string">'jpeg'</span>,<span class="string">'gif'</span>])</span><br></pre></td></tr></table></figure>
<p>关于重命名，重命名有什么用呢？</p>
<blockquote>
<p>一方面个人感觉管理方便，另一方面防御攻击，其实简单的重命名也是可以暴力猜解的</p>
</blockquote>
<h2 id="图片信息筛选"><a href="#图片信息筛选" class="headerlink" title="图片信息筛选"></a>图片信息筛选</h2><p>这里tags可能是多个,一种方法可以在请求解析时设置tags传入 action=’append’<br><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">parser.add_argument(<span class="string">'tags'</span>, type=str, action=<span class="string">'append'</span>)</span><br></pre></td></tr></table></figure></p>
<p>个人感觉我使用的就比较麻烦了，用户传入示例”风景 人物 学校”</p>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> tags:</span><br><span class="line">    <span class="keyword">for</span> tag <span class="keyword">in</span> tags.split(<span class="string">" "</span>): <span class="comment">#空格分隔</span></span><br><span class="line">        <span class="keyword">if</span> models.Tags.query.filter_by(tag=tag.strip()).all():</span><br></pre></td></tr></table></figure>
<blockquote>
<p>首先split对字符串按照空格分隔<br>查询tags是否已经存在，不存在再上传<br>strip()用于移除字符串头尾指定的字符,默认为空格</p>
</blockquote>
<h2 id="多对多关系的储存"><a href="#多对多关系的储存" class="headerlink" title="多对多关系的储存"></a>多对多关系的储存</h2><blockquote>
<p>tags和pictures是设置为多对多关系<br>储存的思路和以前的文章里具体操作一样 <a href="https://bayi87.github.io/2017/08/09/SQLAIchemy%E6%95%B0%E6%8D%AE%E6%A8%A1%E5%9E%8B%E5%85%B3%E8%81%94/#多对多" target="_blank" rel="noopener">多对多关系模型</a><br>具体代码如下</p>
</blockquote>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> tags:</span><br><span class="line">    <span class="keyword">for</span> tag <span class="keyword">in</span> tags.split(<span class="string">" "</span>): <span class="comment">#空格分隔</span></span><br><span class="line">        <span class="keyword">if</span> models.Tags.query.filter_by(tag=tag.strip()).all():</span><br><span class="line">            tagadd = models.Tags.query.filter_by(tag=tag.strip()).all()</span><br><span class="line">            pictures.tags.append(tagadd)</span><br><span class="line">        <span class="keyword">else</span>:    </span><br><span class="line">            tagadd = models.Tags(tag=tag.strip())</span><br><span class="line">            pictures.tags.append(tagadd)</span><br></pre></td></tr></table></figure>
<blockquote>
<p>tags循环查询,append方式添加到pic.tags中</p>
</blockquote>
<h2 id="对外API"><a href="#对外API" class="headerlink" title="对外API"></a>对外API</h2><blockquote>
<p>这部分有模板可循,delete和get没有贴出来<br><a href="http://www.pythondoc.com/Flask-RESTful/reqparse.html" target="_blank" rel="noopener">请求解析介绍</a></p>
</blockquote>
<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">def</span> <span class="title">post_put_parser</span><span class="params">()</span>:</span></span><br><span class="line">	<span class="string">"""</span></span><br><span class="line"><span class="string">	请求解析设置</span></span><br><span class="line"><span class="string">	"""</span></span><br><span class="line">    parse = reqparse.RequestParser()</span><br><span class="line">    parse.add_argument(</span><br><span class="line">        <span class="string">'despriction'</span>, type=str, location=<span class="string">'json'</span>, required=<span class="keyword">True</span>)</span><br><span class="line">    parse.add_argument(</span><br><span class="line">        <span class="string">'address'</span>, type=werkzeug.datastructures.FileStorage,location=<span class="string">'files'</span>, required=<span class="keyword">True</span>)</span><br><span class="line">    parse.add_argument(</span><br><span class="line">        <span class="string">'tags'</span>, type=str, location=<span class="string">'json'</span>, required=<span class="keyword">True</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> parse</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">PicturesAPI</span><span class="params">(Resource)</span>:</span></span><br><span class="line">    <span class="string">"""</span></span><br><span class="line"><span class="string">    上传POST图片的API</span></span><br><span class="line"><span class="string">    """</span></span><br><span class="line"><span class="meta">    @jwt_required()</span></span><br><span class="line"><span class="meta">    @helpers.standardize_api_response</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">post</span><span class="params">(self)</span>:</span></span><br><span class="line"></span><br><span class="line">        parse = post_put_parser()</span><br><span class="line">        args = parse.parse_args()</span><br><span class="line"></span><br><span class="line">        despriction,address,tags = args[<span class="string">'despriction'</span>], args[<span class="string">'address'</span>],args[<span class="string">'tags'</span>]</span><br><span class="line">        <span class="keyword">return</span> controllers.put_or_upload_picture(address,despriction,tags)</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">PictureAPI</span><span class="params">(Resource)</span>:</span></span><br><span class="line">    <span class="string">"""</span></span><br><span class="line"><span class="string">    更新PUT图片的API</span></span><br><span class="line"><span class="string">    """</span></span><br><span class="line"><span class="meta">    @jwt_required()</span></span><br><span class="line"><span class="meta">    @helpers.standardize_api_response</span></span><br><span class="line">    <span class="function"><span class="keyword">def</span> <span class="title">put</span><span class="params">(self)</span>:</span></span><br><span class="line"></span><br><span class="line">        parse = post_put_parser()</span><br><span class="line">        parse.add_argument(<span class="string">'picture_id'</span>, type=str, location=<span class="string">'json'</span>, required=<span class="keyword">True</span>)</span><br><span class="line">        args = parse.parse_args()</span><br><span class="line"></span><br><span class="line">        picture_id,despriction,address,tags = args[<span class="string">'picture_id'</span>],args[<span class="string">'despriction'</span>], args[<span class="string">'address'</span>],args[<span class="string">'tags'</span>]</span><br><span class="line">        <span class="keyword">return</span> controllers.put_or_upload_picture(address,despriction,tags,picture_id)</span><br></pre></td></tr></table></figure>
<h2 id="删除图片"><a href="#删除图片" class="headerlink" title="删除图片"></a>删除图片</h2><pre><code class="python"><span class="function"><span class="keyword">def</span> <span class="title">delete_picture</span><span class="params">(picture_id)</span>:</span>
    <span class="comment">#根据图片id删除相应图片</span>
    picture = models.Picture.query.filter_by(id=picture_id).first()
    <span class="keyword">if</span> <span class="keyword">not</span> picture:
        <span class="keyword">return</span> {<span class="string">'error'</span>: <span class="string">'没有此图片'</span>}
    <span class="keyword">try</span>:
        os.remove(os.path.join(flask.current_app.config[<span class="string">'UPLOAD_FOLDER'</span>],picture.address))
    <span class="keyword">except</span> OSError:
        <span class="keyword">return</span> {<span class="string">"error"</span>: <span class="string">u'文件不存在'</span>}  
    db.session.delete(picture)
    db.session.commit()
    <span class="keyword">return</span> {<span class="string">'deleted'</span>: <span class="string">'图片已删除'</span>}
</code></pre>
<p>PS:<a href="https://github.com/eightHundreds/irides" target="_blank" rel="noopener">完整代码</a></p>

      
    </div>

    
      
      



      
      
    

    
      <footer class="post-footer">
        
          <div class="post-tags">
            
              <a href="/tags/开发/">开发</a>
            
              <a href="/tags/flask/">flask</a>
            
              <a href="/tags/python/">python</a>
            
          </div>
        
        
        
  <nav class="post-nav">
    
      <a class="prev" href="/2017/08/24/看着狗书重构吐槽/">
        <i class="iconfont icon-left"></i>
        <span class="prev-text nav-default">看着狗书重构吐槽</span>
        <span class="prev-text nav-mobile">上一篇</span>
      </a>
    
    
      <a class="next" href="/2017/08/09/SQLAIchemy数据模型关联/">
        <span class="next-text nav-default">SQLAIchemy数据模型关联</span>
        <span class="prev-text nav-mobile">下一篇</span>
        <i class="iconfont icon-right"></i>
      </a>
    
  </nav>

      </footer>
    

  </article>


          </div>
          
  <div class="comments" id="comments">
      <div id="disqus_thread">
        <noscript>
          Please enable JavaScript to view the
          <a href="//disqus.com/?ref_noscript">comments powered by Disqus.</a>
        </noscript>
      </div> 
    </div>
  </div>


        </div>
      </main>

      <footer id="footer" class="footer">

  <div class="social-links">
    
      
        
          <a href="https://github.com/bay1" class="iconfont icon-github" title="github"></a>
        
      
    
      
        
          <a href="http://weibo.com/3190704711/profile?topnav=1&wvr=6&is_all=1" class="iconfont icon-weibo" title="weibo"></a>
        
      
    
      
    
      
    
      
    
    
    
  </div>


<div class="copyright">
  <span class="copyright-year">
    
    &copy; 
     
      2016 - 
    
    2018
    <span class="author">bay1</span>
  </span>
</div>
      </footer>

      <div class="back-to-top" id="back-to-top">
        <i class="iconfont icon-up"></i>
      </div>
    </div>

    
  
  <script type="text/javascript">
    var disqus_config = function () {
        this.page.url = 'https://bay1.top/2017/08/19/如何实现图片上传API/';
        this.page.identifier = '2017/08/19/如何实现图片上传API/';
        this.page.title = '如何实现图片上传API';
    };
    (function() {
    var d = document, s = d.createElement('script');

    s.src = '//https-blog-flywinky-top-1.disqus.com/embed.js';

    s.setAttribute('data-timestamp', +new Date());
    (d.head || d.body).appendChild(s);
    })();  
  </script>



    
  





  
    <script type="text/javascript" src="/lib/jquery/jquery-3.1.1.min.js"></script>
  

  
    <script type="text/javascript" src="/lib/slideout/slideout.js"></script>
  

  
    <script type="text/javascript" src="/lib/fancybox/jquery.fancybox.pack.js"></script>
  


    <script type="text/javascript" src="/js/src/even.js?v=2.6.0"></script>
<script type="text/javascript" src="/js/src/bootstrap.js?v=2.6.0"></script>
<script src="/js/prettify.js"></script>
<script type="text/javascript">
$(document).ready(function(){
 $('pre').addClass('prettyprint');
   prettyPrint();
 })
</script>
  </body>
</html>
